﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: FiniteStateMachine.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2013-01-24 4:34 PM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Linq;
using BOL.Linq.Probability;

namespace BOL.Algorithms.StateMachines
{
    public class FiniteStateMachine<TState, TTransition> : IStateMachine<TState, TTransition>, ICloneable
    {
        #region Public properties

        public IDictionary<TState, double> InitialDistribution { get; set; }

        public IDictionary<TState, IDictionary<TState, TTransition>> TransitionDistribution { get; set; }

        #endregion

        #region Constructors

        public FiniteStateMachine(IDictionary<TState, double> initialDistribution, IDictionary<TState, IDictionary<TState, TTransition>> transitionDistribution)
        {
            if (initialDistribution == null)
                throw new ArgumentNullException("initialDistribution");
            if (transitionDistribution == null)
                throw new ArgumentNullException("transitionDistribution");
            if (initialDistribution.Count == 0)
                throw new ArgumentOutOfRangeException("initialDistribution", "Initial distribution cannot be empty.");
            if (transitionDistribution.Count == 0)
                throw new ArgumentOutOfRangeException("transitionDistribution", "Transition distribution cannot be empty.");

            InitialDistribution = initialDistribution;
            TransitionDistribution = transitionDistribution;
        }

        public FiniteStateMachine(ICollection<TState> states, IDictionary<TState, IDictionary<TState, TTransition>> transitionDistribution)
            : this(states.ToDictionary(s => s, s => s.Equals(states.First()) ? 1.0 : 0.0), transitionDistribution) { }

        #endregion

        #region ICloneable implementation

        public FiniteStateMachine<TState, TTransition> Clone()
        {
            return new FiniteStateMachine<TState, TTransition>(
                InitialDistribution.ToDictionary(x => x.Key, x => x.Value),
                TransitionDistribution.ToDictionary(x => x.Key, x => x.Value.ToDictionary(y => y.Key, y => y.Value) as IDictionary<TState, TTransition>)
                );
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region Public methods

        public IEnumerable<TState> Generate(Random r, int length)
        {
            if (r == null)
                throw new ArgumentNullException("r");

            // Assigns the first state as a begin state.
            var current = InitialDistribution.Sample(r);

            for (var i = 0; i < length; ++i)
            {
                yield return current;

                if (!TransitionDistribution.ContainsKey(current))
                    yield break;

                // Randomly assigns the next state.
                var transitionDistribution = TransitionDistribution[current];
                current = transitionDistribution.Skip(r.Next(transitionDistribution.Count)).FirstOrDefault().Key;
            }
        }

        #endregion

        #region IEquatable<IStateMachine<TState, TTransition>> implementation

        public bool Equals(IStateMachine<TState, TTransition> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            return InitialDistribution.DictionaryEqual(other.InitialDistribution);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return InitialDistribution.OrderBy(x => x.Key).Aggregate(1, (hashCode, x) => hashCode ^ x.Key.GetHashCode());
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is IStateMachine<TState, TTransition>))
                throw new InvalidCastException("The 'other' argument is not a IStateMachine<TState, TTransition> object.");

            return Equals(other as IStateMachine<TState, TTransition>);
        }

        public override string ToString()
        {
            return String.Format("{0} {{States = {{{1}}}, Transitions = {{{2}}}}}",
                GetType().Name,
                String.Join(", ", InitialDistribution.Select(x => String.Format("{0} ({1:G4})", x.Key, x.Value))),
                String.Join(", ", TransitionDistribution.Select(x => String.Join(", ", x.Value.Select(y => String.Format("{0}->{1} ({2})", x.Key, y.Key, y.Value))))));//.Select(y => String.Format("{0} ({1})", y.Key, y.Value))))));
        }

        #endregion
    }
}